/*  ----------------------------------------------------------------------------
 *  Arello Mobile
 *  Fast 3d Engine
 *  ----------------------------------------------------------------------------
 * Except where otherwise noted, this work is licensed under
 * http://creativecommons.org/licenses/by/3.0
 */

#include "AnimatedSequences.h"
#include <Converter.h>
#include <math/utl.h>
#include "../../utl.h"
#include <lib3d/anim2d/SpanSequence.h>
#include <xml/Stream.h>
#include <xml/Saver.h>
#include <boost/bind.hpp>

namespace liba {
namespace lib3d {
namespace anim2d {

const Atom AnimatedSequences::start_name( "Start" );
const Atom AnimatedSequences::frame_percent_name( "Percent" );
const Atom AnimatedSequences::frame_number_name( "Frame" );
const Atom AnimatedSequences::START_ANIMATION("StartAnimation");

const Atom AnimatedSequences::current_time_name( "CurrentTime" );

namespace {
double ReturnZero() { return 0.0; }
} // namespace

AnimatedSequences::AnimatedSequences( const Atom & name )
	: animate::Node( name )
	, currentTime( 0 )
	, active_sequence( 0 )
	, anim_finished(false)
	, current_loop_dir(1)
	, framePercent(&ReturnZero, boost::bind(&AnimatedSequences::SetFramePercent, this, _1))
	, frameNumber(&ReturnZero, boost::bind(&AnimatedSequences::SetFrameNumber, this, _1))
{
	this->RegisterProperty(current_time_name, &this->currentTime);
	this->RegisterProperty(frame_percent_name, &this->framePercent);
	this->RegisterProperty(frame_number_name, &this->frameNumber);
}

AnimatedSequences::AnimatedSequences(const AnimatedSequences& rhs)
	: animate::Node(rhs)
	, currentTime(rhs.currentTime)
	, active_sequence( 0 )
	, current_loop_dir(rhs.current_loop_dir)
	, anim_to_start(rhs.anim_to_start)
	, anim_finished(rhs.anim_finished)
	, framePercent(&ReturnZero, boost::bind(&AnimatedSequences::SetFramePercent, this, _1))
	, frameNumber(&ReturnZero, boost::bind(&AnimatedSequences::SetFrameNumber, this, _1))
{ 
	for (Sequences::const_iterator i = rhs.sequences.begin(); i != rhs.sequences.end(); ++i)
	{
		std::auto_ptr<AbstractSequence> sequence((*i)->clone());
		if (*i == rhs.active_sequence)
			this->active_sequence = sequence.get();
		this->sequences.push_back(sequence.release());
	}

	this->RegisterCommand(START_ANIMATION, &this->startAnimation);
	this->startAnimation.Invoked().connect(boost::bind(&AnimatedSequences::start_animation, this, _1));

	this->RegisterProperty(current_time_name, &this->currentTime);
	this->RegisterProperty(frame_percent_name, &this->framePercent);
	this->RegisterProperty(frame_number_name, &this->frameNumber);
}

AnimatedSequences::~AnimatedSequences()
{
	std::transform( sequences.begin(), sequences.end(), sequences.begin(), utl::delete_ptr< anim2d::AbstractSequence >() );
}

void AnimatedSequences::on_save_nodes(xml::SaverBase * saver) const
{
	Sequences::const_iterator itr = sequences.begin();
	while(itr != sequences.end())
	{
		saver->save_node((*itr)->GetType(), (*itr));

		itr++;
	}
}

bool AnimatedSequences::on_node( xml::Provider * prov, const std::string & name )
{
	if( name == "Sequence" )
	{
		sequences.push_back( new anim2d::Sequence() );
		return prov->parse_object( sequences.back() );
	}
	if( name == "SpanSequence" )
	{
		sequences.push_back( new anim2d::SpanSequence() );
		return prov->parse_object( sequences.back() );
	}
	return false;
}

bool AnimatedSequences::on_attribute( xml::Provider * prov, const std::string & name, const std::string & value )
{
	if( name == "Start" )
	{
		anim_to_start = value;
		return true;
	}
	if( name == "Manual" )
	{
		bool manual = false;
		bool ok = converter::convert(value, &manual);
		if(ok)
			set_manual_time_control(manual);

		return ok;
	}

	return false;
}

bool AnimatedSequences::on_end_node( xml::Provider * prov )
{
	if( anim_to_start.size() )
		start_animation( Atom( anim_to_start ) );
	return true;
}

bool AnimatedSequences::start_animation(const Atom & name)
{
	Sequences::iterator it = sequences.begin();
	for( ; it != sequences.end(); ++it )
	{
		if( (*it)->get_name() == name )
		{
			anim_finished = false;
			active_sequence = *it;
			current_loop_dir = 1;
			currentTime = 0;
			return true;
		}
	}
	return false;
}

void AnimatedSequences::set_manual_time_control(bool manual)
{
	Soul::activate_soul(!manual);
}

void AnimatedSequences::set_time(double time)
{
	if(!active_sequence)
		return;
	
	currentTime = time;
	LifeCycle(0);	//run life_cycle, so frame number will wrap accordingly to the loop types
}

double AnimatedSequences::get_animation_length()const
{
	if(!active_sequence)
		return 0;

	return active_sequence->GetFrameTimes().GetDuration();
}

size_t AnimatedSequences::get_frame_number() const
{
	if (!active_sequence)
		return 0;

	return active_sequence->GetFrameTimes().GetFrameNumber(currentTime);
}

bool AnimatedSequences::needs_life_cycle() const
{
	if (!active_sequence)
		return false;

	return active_sequence->GetFrameTimes().GetFrameCount() > 1;
}

void AnimatedSequences::LifeCycle(time_float delta_time)
{
	anim_finished = false;

	if(!active_sequence || !active_sequence->get_frames_count())
	{
		anim_finished = true;
		return;
	}

	double duration = active_sequence->GetFrameTimes().GetDuration();
	double loopDelay = active_sequence->GetLoopDelay();

	switch(active_sequence->loop_type)
	{
	case anim2d::Sequence::FORWARD:
		currentTime += delta_time;
		currentTime = fmodf(currentTime, duration + loopDelay);
		break;
	case anim2d::Sequence::BACKWARD:
		currentTime -= delta_time;
		if (currentTime < -loopDelay)
			currentTime = fmodf(currentTime, duration + loopDelay) + duration + loopDelay;
		break;
	case anim2d::Sequence::PINGPONG:
		currentTime += current_loop_dir * delta_time;
		while (currentTime < -loopDelay / 2.0f || currentTime > duration + loopDelay / 2.0f)
		{
			currentTime = fmodf(currentTime, (duration + loopDelay) * 2.0);
			if (currentTime < -loopDelay / 2.0f)
				currentTime = -loopDelay - currentTime;
			if (currentTime > duration + loopDelay / 2.0f)
				currentTime = 2.0 * duration + loopDelay - currentTime;
			current_loop_dir = -current_loop_dir;
		}
		break;
	case anim2d::Sequence::NONE:
	default:
		currentTime += delta_time;
		if(delta_time >= 0 && currentTime > duration)
			anim_finished = true;
		
		if (delta_time <= 0 && currentTime < 0)
			anim_finished = true;
	}
}

void AnimatedSequences::disable_loop()
{
	for (Sequences::iterator i = this->sequences.begin(); i != this->sequences.end(); ++i)
		(*i)->loop_type = Sequence::NONE;
}

void AnimatedSequences::SetFramePercent(double value)
{
	//calculate real frame from percent
	if( active_sequence )
		currentTime = value * active_sequence->GetFrameTimes().GetDuration();
}

void AnimatedSequences::SetFrameNumber(double value)
{
	if (active_sequence)
		currentTime = value * active_sequence->GetFrameTimes().GetTime(static_cast<size_t>(value));
}

void AnimatedSequences::render_to_list( hard::Hardware * hardware, hard::RenderTexture * render_texture )
{
	if( !active_sequence )
		return;

	size_t frameNumber = active_sequence->GetFrameTimes().GetFrameNumber(currentTime);

	active_sequence->render_to_list(hardware, render_texture, frameNumber);
}

}
} // namespace lib3d
} // namespace liba

